package cc.aliza.lib.mongo.core.encoder;

import cc.aliza.lib.mongo.core.AlizaEntity;
import cc.aliza.lib.mongo.core.InternalDao;
import cc.aliza.lib.mongo.core.annotations.Default;
import cc.aliza.lib.mongo.core.annotations.RefList;
import cc.aliza.lib.mongo.core.cache.DaoCache;
import cc.aliza.lib.mongo.core.utils.FieldUtil;
import cc.aliza.lib.mongo.core.utils.ReferenceUtil;

import java.lang.reflect.*;
import java.util.*;

/**
 * 『给时光以生命·给岁月以文明』
 *
 * @author Zhang Jing (byrdkm17@gmail.com).
 */
@SuppressWarnings("unchecked")
public class RefListEncoder extends AbstractEncoder {

    private RefList refList;
    private boolean cascadeCU;

    public RefListEncoder(Object obj, Field field) {
        super(obj, field);
        refList = field.getAnnotation(RefList.class);
        String cascade = refList.cascade().toUpperCase();
        cascadeCU = cascade.contains(Default.CASCADE_CREATE) || cascade.contains(Default.CASCADE_UPDATE);
    }

    @Override
    public String getFieldName() {
        return getFieldRealName(refList.name());
    }

    @Override
    public Object encode() {
        Object result = null;
        Class<?> type = field.getType();
        if (type.isArray()) {
            result = encodeArray(type.getComponentType());
        } else {
            ParameterizedType parameterizedType = (ParameterizedType) field.getGenericType();
            Type[] types = parameterizedType.getActualTypeArguments();
            int len = types.length;
            if (len == 1) {
                Class<?> clazz;
                if (types[0] instanceof WildcardType) {
                    clazz = (Class) ((WildcardType) types[0]).getUpperBounds()[0];
                } else {
                    clazz = (Class) types[0];
                }
                result = encodeCollection(clazz);
            }
            if (len == 2) {
                result = encodeMap((Class) types[1]);
            }

        }
        return result;
    }

    private Object encodeArray(Class<?> clazz) {
        List<Object> result = new ArrayList<>();
        int len = Array.getLength(value);
        for (int i = 0; i < len; i++) {
            clazz = FieldUtil.getRealType(field, clazz, Array.get(value, i));
            InternalDao dao = DaoCache.getInstance().get(clazz);
            AlizaEntity entity = (AlizaEntity) Array.get(value, i);
            if (entity != null) {
                if (cascadeCU) {
                    dao.save(entity);
                }
                result.add(ReferenceUtil.toDBRef(entity));
            }
        }
        return result;
    }

    private Object encodeCollection(Class<?> clazz) {
        List<Object> result = new ArrayList<>();
        Collection collection = (Collection) value;
        collection.stream().filter(o -> o != null).forEach(o -> {
            if (cascadeCU) {
                Class<?> cls = FieldUtil.getRealType(field, clazz, o);
                InternalDao dao = DaoCache.getInstance().get(cls);
                dao.save(o);
            }
            result.add(ReferenceUtil.toDBRef((AlizaEntity) o));
        });
        return result;
    }

    private Object encodeMap(Class<?> clazz) {
        Map result = new HashMap<>();
        Map map = (Map) value;
        map.entrySet().stream()
                .filter(o -> ((Map.Entry) o).getValue() != null)
                .forEach(o -> {
                    Map.Entry entry = (Map.Entry) o;
                    if (cascadeCU) {
                        Class<?> cls = FieldUtil.getRealType(field, clazz, entry.getValue());
                        InternalDao dao = DaoCache.getInstance().get(cls);
                        dao.save(entry.getValue());
                    }
                    result.put(entry.getKey(), ReferenceUtil.toDBRef((AlizaEntity) entry.getValue()));
                });
        return result;
    }
}
